﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Net;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using NetworkDetection;
using RestSharp;
using SharpDropBoxClient.ResponseObjects;

namespace SharpDropBoxClient
{
    public class DropBoxRSApp : BaseDropBoxRestClient, INotifyPropertyChanged
    {
        #region IDropBoxApp Members

        public DropBoxRSApp(string apiKey, string apiSecret)

            : this(apiKey, apiSecret, new SterlingDbCache())
        {
        }

        public DropBoxRSApp(string apiKey, string apiSecret, ISterlingDbCache db)
            : this(apiKey, apiSecret, db, NetworkDetector.Instance)
        {
        }

        public DropBoxRSApp(string apiKey, string apiSecret, NetworkDetector netDetector)

            : this(apiKey, apiSecret, new SterlingDbCache(), netDetector)
        {
        }

        public DropBoxRSApp(string apiKey, string apiSecret, ISterlingDbCache db, INetworkDetector netDetector)
        {
            this.apiSecret = apiSecret;
            this.apiKey = apiKey;
            this.db = db;
            this.netDetector = netDetector;
            netDetector.OnNetworkChanged += new EventHandler<NetworkAvailableEventArgs>(netDetector_OnNetworkChanged);
        }

        bool networkIsConnected = true;

        public bool IsConnected
        {
            get
            {
                return networkIsConnected;
            }
        }

        void netDetector_OnNetworkChanged(object sender, NetworkAvailableEventArgs e)
        {
            networkIsConnected = e.IsOnline;
            RaisePropertyChanged("IsConnected");
        }

        public DropBoxAreas DropBoxArea
        {
            get;
            set;
        }

        private string getDropboxSubDir()
        {
            return @"/" + DropBoxArea.ToString();
        }

        public bool NeedAuthInfo
        {
            get
            {
                return !(HaveUserSecret && HaveToken);
            }
        }

        protected RestClient CreateClientWithOauth(string baseURI)
        {
            var client = new RestClient(baseURI);
            client.Authenticator = RestSharp.Authenticators.OAuth1Authenticator.ForProtectedResource(Token, UserSecret, apiKey, apiSecret);

            return client;
        }

        public void LoginAndGetAuthCredentials(string userName, string password, Action<bool, Exception> completeCallback)
        {
            if (networkIsConnected)
            {
                var client = new RestClient(APIBASEURL);

                var request = new RestRequest(TOKENCOMMAND, Method.GET);
                request.AddParameter("oauth_consumer_key", apiKey);
                request.AddParameter("email", userName);
                request.AddParameter("password", password);

                client.ExecuteAsync<TokenResponse>(request, (result) =>
                             {
                                 if ((int)result.StatusCode == 400)
                                 {
                                     completeCallback(false, new DropBoxApiException("Need Mobile Permission", "APP PERMISSION"));
                                 }
                                 else if ((int)result.StatusCode == 401)
                                 {
                                     Token = "";
                                     UserSecret = "";
                                     completeCallback(false, new DropBoxApiException("Need To Re-Login;Stored Credentials have been cleared", "RE-AUTH"));
                                 }
                                 else if (result.ErrorException != null)
                                 {
                                     completeCallback(false, result.ErrorException);
                                 }
                                 else if (result.Data == null)
                                 {
                                     Token = "";
                                     UserSecret = "";
                                     completeCallback(false, new DropBoxApiException("Unable to log in (invalid request)", "RE-AUTH"));
                                 }
                                 else
                                 {
                                     Token = result.Data.token;
                                     UserSecret = result.Data.secret;
                                     completeCallback(true, null);
                                 }
                             });
            }
            else
                throw new DropboxNetworkNotAvailable();
        }

        public void GetAccountInfo(Action<AccountInfoResponse, Exception> callback)
        {
            var client = CreateClientWithOauth(APIBASEURL);

            var request = new RestRequest(ACCOUNTINFOCOMMAND, Method.GET);
            client.ExecuteAsync<AccountInfoResponse>(request, (result) =>
                {
                    if (result.ErrorException != null)
                    {
                        callback(null, result.ErrorException);
                    }
                    else
                    {
                        callback((result.Data, null);
                    }
                });
        }

        public void CreateAccount(string email, string pwd, string firstName, string lastName, Action<string, Exception> callback)
        {
            throw new NotImplementedException();
        }

        private string GetDbPathAndCleansePath(ref string path, out string area)
        {
            var pathEndedWithSlash = path.EndsWith(@"/") || path.EndsWith(@"\");
            path = HttpUtility.UrlDecode(path);
            area = getDropboxSubDir();
            path = cleanPath(path);
            var dbPath = db.getDbPathFromAreaAndPath(area, path);

            dbPath += (pathEndedWithSlash && !dbPath.EndsWith(@"/")) ? "/" : "";
            return dbPath;
        }

        private static void AddParamsToRequest(System.Collections.Generic.Dictionary<string, string> extraparms, RestRequest request)
        {
            foreach (var pair in extraparms)
            {
                request.AddParameter(pair.Key, pair.Value);
            }
            request.AddParameter("oauth_callback", "oob");
        }

        private string ConcatUriPath(params object[] args)
        {
            var sb = new StringBuilder();
            for (int x = 0; x < args.Length; x++)
            {
                var arg = (string)args[0];

                if (x + 1 < args.Length && arg.EndsWith("/"))
                    arg = arg.Substring(0, arg.Length - 1);
                if (x > 0 && arg.StartsWith("/"))
                    arg = arg.Substring(1);
                sb.Append(((x == 0) ? arg : "/" + arg));
            }
            return sb.ToString();
        }

        public void GetFileEntry(string filePath, System.Collections.Generic.Dictionary<string, string> extraparms, Action<DirResult, bool, Exception> callback)
        {
            if (extraparms.ContainsKey("list"))
            {
                extraparms["list"] = "false"; // always false for this call (unnecessary)
            }
            else
            {
                extraparms.Add("list", "false");
            }
            GetMetaData(path, extraparms, callback);
        }

        private void CleanupCachedFiles(DirResult data, string area, DirResult cachedValue)
        {
            //TODO
        }

        public void ListDirAndContents(string path, Dictionary<string, string> extraparms, Action<DirResult, bool, Exception> callback)
        {
            if (extraparms.ContainsKey("list"))
            {
                extraparms["list"] = "true"; // always true for this call (unnecessary)
            }

            else
            {
                extraparms.Add("list", "true");
            }
            GetMetaData(path, extraparms, callback);
        }

        public void GetMetaData(string fsPath, Dictionary<string, string> extraparms, Action<DirResult, bool, Exception> callback)
        {
            var area = "";
            var dbPath = GetDbPathAndCleansePath(ref fsPath, out area);

            var cachedValue = db.getDirResult(dbPath);

            if (cachedValue == null)
            {
                cachedValue = new DirResult { path = fsPath };
            }
            else if (!String.IsNullOrEmpty(cachedValue.hash))
            {
                extraparms.Add("hash", cachedValue.hash);
            }
            if (networkIsConnected)
            {
                var client = CreateClientWithOauth(APIBASEURL);

                var request = new RestRequest(ConcatUriPath(LISTCOMMAND, area, fsPath), Method.GET);
                AddParamsToRequest(extraparms, request);

                client.ExecuteAsync<DirResult>(request, (result) =>
                    {
                        if ((int)result.StatusCode == 304 || (String.IsNullOrEmpty(result.Content) && cachedValue != null)) // No Changes
                        {
                            callback(cachedValue, true, null);
                        }

                        else if ((int)result.StatusCode == 400)
                        {
                            callback(null, false, new DropBoxApiException("This app does not have the necessary permissions to do this.", "APP PERMISSIONS"));
                        }
                        else if ((int)result.StatusCode == 404)
                        {
                            callback(null, false, new FileNotFoundException());
                        }
                        else if ((int)result.StatusCode == 406)
                        {
                            callback(null, false, new IOException("Too many files to return"));
                        }
                        else if (result.ErrorException != null)
                        {
                            callback(null, false, result.ErrorException);
                        }
                        else
                        {
                            if (cachedValue != null)
                            {
                                CleanupCachedFiles(result.Data, area, cachedValue);
                            }
                            db.SaveDirResult(result.Data);

                            callback(result.Data, false, null);
                        }
                    });
            }
        }

        public System.Collections.Generic.IDictionary<string, object> GetLastDirInfoResults(string path)
        {
            throw new NotImplementedException();
        }

        public DBoxListing GetLastListingResults(string path)
        {
            throw new NotImplementedException();
        }

        public void GetFile(string path, Action<string, Exception> callback)
        {
            throw new NotImplementedException();
        }

        public void GetFileURI(string path, Action<string, Exception> callback)
        {
            throw new NotImplementedException();
        }

        public void GetThumbbnail(string path, Action<System.IO.Stream, Exception> callback)
        {
            throw new NotImplementedException();
        }

        public void GetThumbbnail(string path, DropBoxApp.ThumbnailSize size, Action<System.IO.Stream, Exception> callback)
        {
            throw new NotImplementedException();
        }

        public void GetThumbbnail(string path, DropBoxApp.ThumbnailFormat format, Action<System.IO.Stream, Exception> callback)
        {
            throw new NotImplementedException();
        }

        public void GetThumbbnail(string path, DropBoxApp.ThumbnailSize size, DropBoxApp.ThumbnailFormat format, Action<System.IO.Stream, Exception> callback)
        {
            throw new NotImplementedException();
        }

        public void GetThumbbnailURI(string path, Action<string, Exception> callback)
        {
            throw new NotImplementedException();
        }

        public void GetThumbbnailURI(string path, DropBoxApp.ThumbnailSize size, Action<string, Exception> callback)
        {
            throw new NotImplementedException();
        }

        public void GetThumbbnailURI(string path, DropBoxApp.ThumbnailFormat format, Action<string, Exception> callback)
        {
            throw new NotImplementedException();
        }

        public void GetThumbbnailURI(string path, DropBoxApp.ThumbnailSize size, DropBoxApp.ThumbnailFormat format, Action<string, Exception> callback)
        {
            throw new NotImplementedException();
        }

        public void UploadFile(string dirPath, string fileName, System.IO.Stream file, Action<Exception> callback)
        {
            throw new NotImplementedException();
        }

        public void DeletePathOrFile(string path, Action<Exception> callback)
        {
            throw new NotImplementedException();
        }

        public void CopyPathOrFile(string srcPath, string destPath, Action<MetadataCallResult> callback)
        {
            throw new NotImplementedException();
        }

        public void MovePathOrFile(string srcPath, string destPath, Action<MetadataCallResult> callback)
        {
            throw new NotImplementedException();
        }

        public void CreateDirectory(string path, Action<MetadataCallResult> callback)
        {
            throw new NotImplementedException();
        }

        #endregion IDropBoxApp Members

        #region INotifyPropertyChanged Members

        // Borrows very heavily from Laurent Bugnion 's MVVM Light
        public event PropertyChangedEventHandler PropertyChanged;

        /// <summary>
        /// Raises the PropertyChanged event if needed, and broadcasts a
        /// PropertyChangedMessage using the Messenger instance (or the
        /// static default instance if no Messenger instance is available).
        /// </summary>
        /// <typeparam name="T">The type of the property that
        /// changed.</typeparam>
        /// <param name="propertyName">The name of the property that
        /// changed.</param>
        /// <param name="oldValue">The property's value before the change
        /// occurred.</param>
        /// <param name="newValue">The property's value after the change
        /// occurred.</param>
        /// <param name="broadcast">If true, a PropertyChangedMessage will
        /// be broadcasted. If false, only the event will be raised.</param>
        [SuppressMessage("Microsoft.Design", "CA1030:UseEventsWhereAppropriate",
            Justification = "This cannot be an event")]
        protected virtual void RaisePropertyChanged<T>(string propertyName, T oldValue, T newValue, bool broadcast)
        {
            RaisePropertyChanged(propertyName);
        }

        /// <summary>
        /// Raises the PropertyChanged event if needed.
        /// </summary>
        /// <param name="propertyName">The name of the property that
        /// changed.</param>
        [SuppressMessage("Microsoft.Design", "CA1030:UseEventsWhereAppropriate",
            Justification = "This cannot be an event")]
        protected virtual void RaisePropertyChanged(string propertyName)
        {
            VerifyPropertyName(propertyName);

            var handler = PropertyChanged;

            if (handler != null)
            {
                handler(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        /// <summary>
        /// Verifies that a property name exists in this ViewModel. This method
        /// can be called before the property is used, for instance before
        /// calling RaisePropertyChanged. It avoids errors when a property name
        /// is changed but some places are missed.
        /// <para>This method is only active in DEBUG mode.</para>
        /// </summary>
        /// <param name="propertyName"></param>
        [Conditional("DEBUG")]
        [DebuggerStepThrough]
        public void VerifyPropertyName(string propertyName)
        {
            var myType = this.GetType();
            if (myType.GetProperty(propertyName) == null)
            {
                throw new ArgumentException("Property not found", propertyName);
            }
        }

        #endregion INotifyPropertyChanged Members
    }
}